﻿#include "ipv4.h"
#include "debug.h"
#include "tools.h"
#include "protocol.h"
#include "netif.h"
#include "icmpv4.h"
#include "ipv4_frag.h"
#include "raw.h"
#include "routetbl.h"
#include "udp.h"

static uint16_t packet_id = 0;

#if DBG_DISP_ENABLED(DBG_IPV4_LEVEL)
static void display_ip_pkt(ipv4_pkt_t *pkt)
{
    ipv4_hdr_t *hdr = &pkt->hdr;
    plat_printf("=================ipv4 pkt=====================\n");
    plat_printf("version:%d  header_len:%d  total_len:%d  id:%d  ttl:%d  protocol:%04x checksum:%04x\n",
                hdr->version, hdr->shdr, hdr->total_len, hdr->id, hdr->ttl, hdr->protocol, hdr->hdr_checksum);
    ipaddr_t dst_ip = ipaddr_from_buf(hdr->dst_ip, hdr->version);
    ipaddr_t src_ip = ipaddr_from_buf(hdr->src_ip, hdr->version);
    dbg_dump_ipaddr("src_ipaddr:", &src_ip);
    dbg_dump_ipaddr("dst_ipaddr:", &dst_ip);
    plat_printf("=============================================\n");
}
#else
#define display_ip_pkt(pkt)
#endif

/**
 * @brief 转换从网络收到的ipv4包大小端格式
 */
static void ipv4_pkt_ntoh(ipv4_pkt_t *pkt)
{
    pkt->hdr.total_len = x_ntohs(pkt->hdr.total_len);
    pkt->hdr.id = x_ntohs(pkt->hdr.id);
    pkt->hdr.frag_all = x_ntohs(pkt->hdr.frag_all);
}

/**
 * @brief 转换从发送的ipv4包大小端格式
 */
static void ipv4_pkt_hton(ipv4_pkt_t *pkt)
{
    pkt->hdr.total_len = x_htons(pkt->hdr.total_len);
    pkt->hdr.id = x_htons(pkt->hdr.id);
    pkt->hdr.frag_all = x_htons(pkt->hdr.frag_all);
}

static void ipv4_set_hdrsize(ipv4_pkt_t *pkt, int len)
{
    pkt->hdr.shdr = len / 4;
}

/**
 * @brief  判断ipv4数据包的合法性
 * @param pkt 收到的数据包
 * @param size 数据包的大小
 * @param netif 收到该数据包的网络接口
 */
static bool ipv4_is_pkt_ok(ipv4_pkt_t *pkt, int size, netif_t *netif)
{
    // 判断是否为IPV4版本
    if (pkt->hdr.version != NET_VERSION_IPV4)
    {
        DBG_WARNING(DBG_IPV4_LEVEL, "the ip version: %d does't supported", pkt->hdr.version);
        return false;
    }

    // 判断报文长度是否合法
    int hdr_len = ipv4_hdr_size(pkt);
    if (hdr_len < sizeof(ipv4_hdr_t))
    {
        DBG_WARNING(DBG_IPV4_LEVEL, "the ipv4 pkt hdr size:%d too short!", hdr_len);
        return false;
    }

    int total_size = x_ntohs(pkt->hdr.total_len);
    if (total_size < sizeof(ipv4_hdr_t) || (size < total_size))
    {
        DBG_WARNING(DBG_IPV4_LEVEL, "the ipv4 pkt size:%d is error!", total_size);
        return false;
    }

    if (pkt->hdr.hdr_checksum)
    {
        uint16_t c = tools_checksum16(0, pkt, hdr_len, 0, true);
        if (c)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "check by hdr_checksum, discover some err in pkt!");
            return NET_ERR_BROKEN;
        }
    }

    ipaddr_t dest_ipaddr = ipaddr_from_buf(pkt->hdr.dst_ip, pkt->hdr.version);
    if (!ipaddr_is_match(&dest_ipaddr, &netif->ipaddr, &netif->netmask))
    {
        DBG_INFO(DBG_IPV4_LEVEL, "the ipv4 pkt is not for me!");
    }

    return true;
}

/**
 * @brief ipv4初始化
 */
net_err_t ipv4_init()
{
    DBG_INFO(DBG_IPV4_LEVEL, "ipv4 init begin!");

    net_err_t err = ipfrag_init();
    if (err < NET_ERR_OK)
    {
        DBG_ERROR(DBG_IPV4_LEVEL, "ipv4 init failed!");
        return err;
    }

    DBG_INFO(DBG_IPV4_LEVEL, "ipv4 init end!");
    return NET_ERR_OK;
}

/**
 * @brief 对于经过校验符合要求的收到的ipv4包进行处理
 * @param netif 网络接口
 * @param buf 数据包
 * @param dst_ip 收到的数据包的目的ip地址
 * @param src_ip 收到的数据包的源ip地址
 */
net_err_t ipv4_normal_in(netif_t *netif, pktbuf_t *buf, ipaddr_t *dst_ip, ipaddr_t *src_ip)
{
    ipv4_pkt_t *pkt = (ipv4_pkt_t *)pktbuf_data(buf);
    switch (pkt->hdr.protocol)
    {
    case NET_PROTOCOL_ICMPv4:
    {
        DBG_INFO(DBG_IPV4_LEVEL, "recv ipv4 pkt: icmpv4!");
        net_err_t err = icmpv4_in(src_ip, dst_ip, buf);
        if (err < NET_ERR_OK)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "ipv4 in send pkt to icmpv4 in failed!");
            return err;
        }
        return NET_ERR_OK;
        break;
    }
    case NET_PROTOCOL_UDP:
    {
        DBG_INFO(DBG_IPV4_LEVEL, "recv ipv4 pkt: udp!");
        net_err_t err = udp_in(buf, src_ip, dst_ip);
        if (err < NET_ERR_OK)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "ipv4_in put pkt to udp failed!");
            if (NET_ERR_UNREACH == err)
            {
                ipv4_pkt_ntoh(pkt); // 因为后面要copy收到的包,所以这里要把收到的包恢复原样
                icmpv4_out_unreach(src_ip, dst_ip, ICMPV4_UNREACH_PORT, buf);
            }
            pktbuf_free(buf);
        }
        return NET_ERR_OK;
        break;
    }
    case NET_PROTOCOL_TCP:
        DBG_INFO(DBG_IPV4_LEVEL, "recv ipv4 pkt: tcp!");
        pktbuf_free(buf);
        return NET_ERR_OK;
        break;
    default:
        DBG_WARNING(DBG_IPV4_LEVEL, "Unkown protocol:%d of the ipv4 pkt!", pkt->hdr.protocol);
        return raw_in(buf);
        break;
    }

    return NET_ERR_UNREACH;
}

/**
 * @brief 对收到的ipv4的协议包进行处理
 * @param netif 收到包的网络接口
 * @param buf 收到的包
 */
net_err_t ipv4_in(netif_t *netif, pktbuf_t *buf)
{
    DBG_INFO(DBG_IPV4_LEVEL, "netif:%s ipv4 in!", netif->name);

    net_err_t err = pktbuf_set_cont(buf, sizeof(ipv4_pkt_t));
    if (err < NET_ERR_OK)
    {
        DBG_ERROR(DBG_ARP_LEVEL, "set cont of the buf size:%d failed!", buf->total_size);
        return err;
    }

    // 判断收到的包是否合法
    ipv4_pkt_t *ipv4_pkt = (ipv4_pkt_t *)pktbuf_data(buf);

    // 在没对包校验前,千万不能更改内存的内容以及大小端格式
    pktbuf_reset_acc(buf);
    if (!ipv4_is_pkt_ok(ipv4_pkt, buf->total_size, netif))
    {
        DBG_WARNING(DBG_ARP_LEVEL, "the ipv4 pkt is error!");
        return NET_ERR_NOSUPPORT;
    }
    // 校验完以后在检验
    ipv4_pkt_ntoh(ipv4_pkt);
    display_ip_pkt(ipv4_pkt);

    // 去除其他协议填充的无用内容
    err = pktbuf_resize(buf, ipv4_pkt->hdr.total_len);
    if (err < NET_ERR_OK)
    {
        DBG_WARNING(DBG_IPV4_LEVEL, "ipv4 pkt resize:%d failed!", ipv4_pkt->hdr.total_len);
        return NET_ERR_SIZE;
    }

    ipaddr_t dst_ip = ipaddr_from_buf(ipv4_pkt->hdr.dst_ip, ipv4_pkt->hdr.version);
    ipaddr_t src_ip = ipaddr_from_buf(ipv4_pkt->hdr.src_ip, ipv4_pkt->hdr.version);
    // 判断收到的数据包是不是分片数据包
    if (ipv4_pkt->hdr.offset || ipv4_pkt->hdr.more)
    {
        err = ipfrag_in(netif, buf, &src_ip, &dst_ip);
        if (err < NET_ERR_OK)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "ipv4 frag in failed!");
            pktbuf_free(buf);
        }
    }
    else
    {
        err = ipv4_normal_in(netif, buf, &dst_ip, &src_ip);
        if (err < NET_ERR_OK)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "ipv4 pkt normal in failed!");
            pktbuf_free(buf);
        }
    }

    return NET_ERR_OK;
}

/**
 * @brief ip数据包分片发送
 * @param protocol ipv4中的协议类型
 * @param dest 目的ip地址
 * @param src 源ip地址
 * @param buf 要发送的数据包
 * @param netif 进行发送操作的网络接口
 */
static net_err_t ipv4_frag_out(uint8_t protocol, ipaddr_t *dest,
                               ipaddr_t *src, pktbuf_t *buf, netif_t *netif)
{
    DBG_INFO(DBG_IPV4_LEVEL, "ipv4 send buf:%d frag!", buf->total_size);
    pktbuf_reset_acc(buf);

    int offset = 0;
    int total = buf->total_size;
    while (total)
    {
        int curr_size = total;
        curr_size = curr_size > netif->mtu - sizeof(ipv4_hdr_t) ? netif->mtu - sizeof(ipv4_hdr_t) : curr_size;

        // 非最后一块，将ip数据包负载部分大小调整为8字节对齐
        // 在以太网上，除ip包头外，数据负载为1480字节，是对齐的，但其他不一定对齐
        if (curr_size < total)
        {
            curr_size &= ~0x7;
        }

        pktbuf_t *dst_buf = pktbuf_alloc(curr_size + sizeof(ipv4_hdr_t));
        if (!dst_buf)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "alloc buf:%d failed!", curr_size + sizeof(ipv4_hdr_t));
            return NET_ERR_FULL;
        }

        // 初始化包头
        if (pktbuf_set_cont(dst_buf, sizeof(ipv4_hdr_t)) < NET_ERR_OK)
        {
            DBG_ERROR(DBG_IPV4_LEVEL, "ipv4 frag set cont:%d failed!", sizeof(ipv4_hdr_t));
            return NET_ERR_SYS;
        }
        ipv4_pkt_t *pkt = (ipv4_pkt_t *)pktbuf_data(dst_buf);
        pkt->hdr.shdr_all = 0;
        pkt->hdr.version = NET_VERSION_IPV4;
        ipv4_set_hdrsize(pkt, sizeof(ipv4_hdr_t));
        pkt->hdr.total_len = dst_buf->total_size;
        pkt->hdr.id = packet_id;
        pkt->hdr.frag_all = 0;
        pkt->hdr.ttl = NET_IP_DEFAULT_TTL;
        pkt->hdr.hdr_checksum = 0; // 这里校验和一定要先初始化为0，不然计算的时候相加会有影响
        pkt->hdr.protocol = protocol;
        ipaddr_to_buf(dest, pkt->hdr.dst_ip);
        if (!src && !ipaddr_is_any(src))
        {
            ipaddr_to_buf(src, pkt->hdr.src_ip);
        }
        else
        {
            ipaddr_to_buf(&netif->ipaddr, pkt->hdr.src_ip);
        }

        // 调整偏移量
        pkt->hdr.offset = offset >> 3; // 8字节为单位
        pkt->hdr.more = total > curr_size;

        pktbuf_seek(dst_buf, sizeof(ipv4_hdr_t));
        net_err_t err = pktbuf_copy(dst_buf, buf, curr_size);
        if (err < NET_ERR_OK)
        {
            DBG_ERROR(DBG_IPV4_LEVEL, "copy buf failed in ip frag out!");
            pktbuf_free(dst_buf);
            return err;
        }

        // 释放已经拷贝的部分，减少内存使用
        pktbuf_remove_header(buf, curr_size);
        pktbuf_reset_acc(buf);

        display_ip_pkt(pkt);

        ipv4_pkt_hton(pkt);
        pktbuf_seek(dst_buf, 0);
        pkt->hdr.hdr_checksum = pktbuf_checksum16(dst_buf, ipv4_hdr_size(pkt), 0, true);

        // 通过网络接口发送
        route_entry_t *route_entry = routetbl_find(dest);
        ipaddr_t next_hop; // 下一跳地址
        if (ipaddr_is_any(&route_entry->next_hop))
        {
            // 路由表中下一跳为缺省值，则直接传送
            ipaddr_copy(&next_hop, dest);
        }
        else
        {
            // 否则走网关
            ipaddr_copy(&next_hop, &route_entry->next_hop);
        }
        err = netif_out(netif, &next_hop, dst_buf);
        if (err < NET_ERR_OK)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "netif out failed in ip frag!");
            pktbuf_free(dst_buf);
            return err;
        }

        total -= curr_size;
        offset += curr_size;
    }

    ++packet_id;
    pktbuf_free(buf);

    return NET_ERR_OK;
}

/**
 * @brief ipv4协议向外发出数据包
 * @param protocol ipv4中的协议类型
 * @param dst_ip 目的ip地址
 * @param src_ip 源ip地址
 * @param buf 将要发送的数据包
 */
net_err_t ipv4_out(uint8_t protocol, ipaddr_t *dst_ip, ipaddr_t *src_ip, pktbuf_t *buf)
{
    DBG_INFO(DBG_IPV4_LEVEL, "ipv4 out!");

    // 查找路由表，获取下一跳地址
    route_entry_t *route_entry = routetbl_find(dst_ip);
    if (!route_entry)
    {
        DBG_ERROR(DBG_IPV4_LEVEL, "can not find route entry!");
        return NET_ERR_UNREACH;
    }
    netif_t *netif = route_entry->netif;

    // 判断需不需要进行分片发送
    if (netif->mtu && buf->total_size + sizeof(ipv4_hdr_t) > netif->mtu)
    {
        net_err_t err = ipv4_frag_out(protocol, dst_ip, src_ip, buf, netif);
        if (err < 0)
        {
            DBG_WARNING(DBG_IPV4_LEVEL, "ipv4 frag out failed!");
            return err;
        }

        return NET_ERR_OK;
    }

    // 添加包头并初始化包头,转换大小端后,交给下层处理
    net_err_t err = pktbuf_add_header(buf, sizeof(ipv4_hdr_t), true);
    if (err < NET_ERR_OK)
    {
        DBG_ERROR(DBG_IPV4_LEVEL, "add headr of ipv4 failed, err type:%d !", err);
        return err;
    }

    ipv4_pkt_t *pkt = (ipv4_pkt_t *)pktbuf_data(buf);
    pkt->hdr.shdr_all = 0;
    pkt->hdr.version = NET_VERSION_IPV4;
    ipv4_set_hdrsize(pkt, sizeof(ipv4_hdr_t));
    pkt->hdr.total_len = buf->total_size;
    pkt->hdr.id = packet_id++;
    pkt->hdr.frag_all = 0;
    pkt->hdr.hdr_checksum = 0;
    pkt->hdr.ttl = NET_IP_DEFAULT_TTL;
    pkt->hdr.protocol = protocol;
    if (!src_ip && !ipaddr_is_any(src_ip))
    {
        ipaddr_to_buf(src_ip, pkt->hdr.src_ip);
    }
    else
    {
        ipaddr_to_buf(&netif->ipaddr, pkt->hdr.src_ip);
    }

    ipaddr_to_buf(dst_ip, pkt->hdr.dst_ip);

    display_ip_pkt(pkt);

    // 大小端转换  注意,先转换再计算校验和,因为真正到时候计算的是大端存放
    ipv4_pkt_hton(pkt);
    pktbuf_reset_acc(buf);
    pkt->hdr.hdr_checksum = pktbuf_checksum16(buf, sizeof(ipv4_hdr_t), 0, true);

    // 通过网络接口发送
    ipaddr_t next_hop; // 下一跳地址
    if (ipaddr_is_any(&route_entry->next_hop))
    {
        // 路由表中下一跳为缺省值，则直接传送
        ipaddr_copy(&next_hop, dst_ip);
    }
    else
    {
        // 否则走网关
        ipaddr_copy(&next_hop, &route_entry->next_hop);
    }
    err = netif_out(netif, &next_hop, buf);
    if (err < NET_ERR_OK)
    {
        DBG_WARNING(DBG_IPV4_LEVEL, "ipv4 out send ipv4 pkt by netif:%s failed!", netif->name);
        return err; // 这里需不需要释放包?交给调用者判断
    }

    return NET_ERR_OK;
}